#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/types.h>
#include "../utils/Utils.h"
#include "Compressor.h"

using namespace zipUtils;

void Compressor::addFile(char* absolutePath, char* fileName, CompresionMethods compressionMethod)
{
    FILE* file = fopen(absolutePath, "r");
    FileHeader fileHeader(fileName);
    int fileSize = calculateSize(file);
    tm* lastDateTime = getLastModificationTimeAndDate(absolutePath);
    
    fileHeader.setVersionToExtract(10);
    fileHeader.setFlag(0);
    fileHeader.setCompressionMethod(compressionMethod);
    fileHeader.setLastModificationTime(parseTimeToShort(lastDateTime));
    fileHeader.setLastModificationDate(parseDateToShort(lastDateTime));
    fileHeader.setCrc((int) getCRC32(file));
    fileHeader.setUnCompressedSize(fileSize);
    fileHeader.setExtraField("");
    fileHeader.setAbsolutePath(absolutePath);
    
    switch(compressionMethod)
    {
        case nonCompresion: fileHeader.setCompressedSize(fileSize);
                            char* fileBuffer = (char*) malloc(fileSize * sizeof (char));
                            memset(fileBuffer, 0, fileSize);
                            getBytes(file, fileBuffer, fileSize);
                            fileHeader.setFileData(fileBuffer, fileSize);
                            free(fileBuffer);
                            break;
        
    }
    fclose(file);
    fileHeader.buildFileHeader();
    fileHeaders_->add(fileHeader);
    headersSize_ += fileHeader.getBufferSize();
    fileHeadersNamesLength += fileHeader.getFileNameLength();
    fileHeaderExtraFieldLength += fileHeader.getExtraFieldLentgth();
}

void Compressor::addDirectory(char* absolutePath, char* fileName, CompresionMethods compressionMethod)
{
    DIR *dpdf;
    struct dirent *epdf;
    dpdf = opendir(absolutePath);
    
    if (dpdf !=NULL) 
    {
        readdir(dpdf);
        readdir(dpdf);
        while (epdf = readdir(dpdf)) 
        {
            char* path = (char*) malloc(strlen(absolutePath) + strlen(epdf->d_name) + 1);
            char* relativePath = (char*) malloc(strlen(fileName) + strlen(epdf->d_name) + 2);
            memset(path, 0, strlen(absolutePath) + strlen(epdf->d_name) + 1);
            memset(relativePath, 0, strlen(fileName) + strlen(epdf->d_name) + 2);
            
            strcat(relativePath, fileName);
            strcat(relativePath, "/");
            strcat(relativePath, epdf->d_name);
            
            strcat(path, absolutePath);
            strcat(path, "/");
            strcat(path, epdf->d_name); 
            
            printf("Filename: %s\n", epdf->d_name);
            if(isFile(path))
            {
                addFile(path, relativePath, compressionMethod);
            }
            else
            {
                addDirectory(path, relativePath, compressionMethod);
                strcat(relativePath, "/");
                addFile(path, relativePath, compressionMethod);
            }
//            free(path);
//            free(relativePath);
        }
    }
}

void Compressor::compress(const char* sourcePath,const char* destinyPath, int compressionMethod)
{
    char* source=(char*)malloc(strlen(sourcePath)+1);
    char* dest=(char*)malloc(strlen(destinyPath)+1);
    strcpy(source,sourcePath);
    source[strlen(sourcePath)]=0;
    strcpy(dest,destinyPath);
    dest[strlen(destinyPath)]=0;

    char* fileName = source + (lastIndexOf(source, '/') + 1);
    CompresionMethods cm;
    switch(compressionMethod)
    {
        case 0: cm = nonCompresion;
        break;
        case 8: cm = deflate;
        break;
    }
    if(isFile(source))
    {
        addFile(source, fileName, cm);
    }
    else
    {
        addDirectory(source, fileName, cm);
    }
    
    char* outName = (char*) malloc(strlen(dest) + strlen(fileName) + 5);
    memset(outName, 0, strlen(dest) + strlen(fileName) + 5);
    strcat(outName, dest);
    strcat(outName, "/");
    strcat(outName, fileName);
    strcat(outName, ".zip");
    FILE* file = fopen(outName, "w");
    char* buffer = 0;
    buildZipFormat(buffer);
    fwrite(buffer, sizeof(char), getZipFileLength(), file);
    fclose(file);
    free(outName);
    free(buffer);
}

void Compressor::buildZipFormat(char*& buffer)
{
    int size = headersSize_ + (46 * fileHeaders_->size()) + fileHeadersNamesLength +
            fileHeaderExtraFieldLength + 22;
    buffer = (char*) malloc(size);
    memset(buffer, 0, size);
    int aux = 0;
    int fileHeaderOffset = 0;
    //Build file headers
    for(int i = 0; i < fileHeaders_->size(); i++)
    {
        FileHeader fileHeader = fileHeaders_->get(i);
        conCat(buffer, fileHeader.getBuffer(), aux, fileHeader.getBufferSize());
        aux += fileHeader.getBufferSize();
    }
    
    offsetOfCentralDirectory_ = aux;
    //Build central directory entries
    for (int i = 0; i < fileHeaders_->size(); i++)
    {
        FileHeader fileHeader = fileHeaders_->get(i);
        char* centralDirectory = 0;
        buildCentralDirectoryFileHeader(fileHeader, fileHeaderOffset, centralDirectory);
        size_t centralDirectorySize = 46 + fileHeader.getFileNameLength() + fileHeader.getExtraFieldLentgth();
        conCat(buffer, centralDirectory, aux, centralDirectorySize);
        aux += centralDirectorySize;
        fileHeaderOffset += fileHeader.getBufferSize();
    }
    centralDirectorySize_ = aux - offsetOfCentralDirectory_;
    //Build the end of the central directory
    char* endOfCentralDirectory;
    buildEndOfCentralDirectory(endOfCentralDirectory);
    conCat(buffer, endOfCentralDirectory, aux, 22);
}

int Compressor::getHeadersLength()
{
    return headersSize_;
}

int Compressor::getCentralDirectorySize()
{
    return centralDirectorySize_;
}

int Compressor::getZipFileLength()
{
    return headersSize_ + centralDirectorySize_ + 22;
}

void Compressor::buildCentralDirectoryFileHeader(FileHeader &fh, int fileHeaderOffset, char*& buffer)
{
    short versionMadeBy = 31;
    short versionToExtract = fh.getVersionToExtract();
    short flag = fh.getFlag();
    short compressionMethod = fh.getCompressionMethod();
    short time = fh.getLastModificationTime();
    short date = fh.getLastModificationDate();
    int crc = fh.getCrc();
    int compressedSize = fh.getCompressedSize();
    int uncompressedSize = fh.getUncompressedSize();
    short fileNameLength = fh.getFileNameLength();
    short extraFieldLength = fh.getExtraFieldLentgth();
    short commentLength = 0;
    short diskStart = 0;
    short internalAttributes = 0;
    int externalAttributes = 0;
    int relativeOffset = fileHeaderOffset;

    buffer = (char*) malloc(46 + fh.getFileNameLength() + fh.getExtraFieldLentgth());
    memset(buffer, 0, 46 + fh.getFileNameLength() + fh.getExtraFieldLentgth());
    copy(buffer, &centralDirectoryHeader_, 0, sizeof(centralDirectoryHeader_));
    copy(buffer, &versionMadeBy, 4, sizeof(versionMadeBy));
    copy(buffer, &versionToExtract, 6, sizeof(versionToExtract));
    copy(buffer, &flag, 8, sizeof(flag));
    copy(buffer, &compressionMethod, 10, sizeof(compressionMethod));
    copy(buffer, &time, 12, sizeof(time));
    copy(buffer, &date, 14, sizeof(date));
    copy(buffer, &crc, 16, sizeof(crc));
    copy(buffer, &compressedSize, 20, sizeof(compressedSize));
    copy(buffer, &uncompressedSize, 24, sizeof(uncompressedSize));
    copy(buffer, &fileNameLength, 28, sizeof(fileNameLength));
    copy(buffer, &extraFieldLength, 30, sizeof(extraFieldLength));
    copy(buffer, &commentLength, 32, sizeof(commentLength));
    copy(buffer, &diskStart, 34, sizeof(diskStart));
    copy(buffer, &internalAttributes, 36, sizeof(internalAttributes));
    copy(buffer, &externalAttributes, 38, sizeof(externalAttributes));
    copy(buffer, &relativeOffset, 42, sizeof(relativeOffset));
    conCat(buffer, fh.getFileName(), 46, fileNameLength);
    conCat(buffer, fh.getExtraField(), 46 + fileNameLength, extraFieldLength);
}

void Compressor::buildEndOfCentralDirectory(char*& buffer)
{
    buffer = (char*) malloc(22);
    memset(buffer, 0, 22);
    short numberOfDisk = 0;
    short centralDirectoryStart = 0;
    short cDRecords = fileHeaders_->size();
    short numberOfCDRecords = fileHeaders_->size();
    short commentLength = 0;
    
    copy(buffer, &endOfCentralDirectoryHeader_, 0, sizeof(endOfCentralDirectoryHeader_));
    copy(buffer, &numberOfDisk, 4, sizeof(numberOfDisk));
    copy(buffer, &centralDirectoryStart, 6, sizeof(centralDirectoryStart));
    copy(buffer, &cDRecords, 8, sizeof(cDRecords));
    copy(buffer, &numberOfCDRecords, 10, sizeof(numberOfCDRecords));
    copy(buffer, &centralDirectorySize_, 12, sizeof(centralDirectorySize_));
    copy(buffer, &offsetOfCentralDirectory_, 16, sizeof(offsetOfCentralDirectory_));
    copy(buffer, &commentLength, 20, sizeof(commentLength));
}
